﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace uTILLIty.Rave.Core.Manager
{
  /// <summary>
  /// Reads a line from a stream and parses it as CSV
  /// </summary>
  /// <remarks>
  /// Ignores lines starting with #. Enquoted values can be multi-line.
  /// </remarks>
	public class CsvManager
	{
		private static readonly Regex XmlEscapeRegex = new Regex(@"(?i)\<x\>(?<xml>[^\<]+)\</x\>");

    /// <summary>
    /// Default: semicolon (;)
    /// </summary>
		public string CsvSeparator { get; set; }

    /// <summary>
    /// Creates a default instance
    /// </summary>
		public CsvManager()
		{
			CsvSeparator = ";";
		}

    /// <summary>
    /// Reads the next record from the stream
    /// </summary>
		public virtual string[] ReadNextRecord(StreamReader csv)
		{
			while (true)
			{
				if (csv.EndOfStream)
					return null;
				var line = csv.ReadLine();
				if (string.IsNullOrWhiteSpace(line))
					continue;
				if (line.StartsWith("#"))
					continue;
				var values = SplitCsvLine(csv, line, CsvSeparator[0]);
				return values;
			}
		}

    /// <summary>
    /// Reads bytes from the stream until a CSV record/line has been extracted
    /// </summary>
    /// <remarks>Enquoted values can be multi-line.</remarks>
		internal protected virtual string[] SplitCsvLine(StreamReader csv, string line, char csvSeparator)
		{
			var values = new List<string>(20);
			var curValue = "";
			bool inText = false;

			while (true)
			{
				foreach (var curChar in line)
				{
					if (!inText && curChar == csvSeparator)
					{
						values.Add(curValue);
						curValue = "";
						continue;
					}
					if (curChar == '"')
					{
						inText = !inText;
						continue;
					}
					curValue += curChar;
				}
				if (!inText)
					break;
				line = csv.ReadLine(); //in quoted text, continue processing on next line
				if (string.IsNullOrWhiteSpace(line))
					break;
				curValue += Environment.NewLine;
			}
			values.Add(curValue);
			return values.ToArray();
		}

		/// <summary>
		/// XML-Escapes the value (required for injecting it into the template)
		/// </summary>
    public virtual string XmlEscape(string value, Encoding encoding)
		{
			using (var stream = new MemoryStream(value.Length * 2))
			{
				using (var xtw = new XmlTextWriter(stream, encoding))
				{
					xtw.WriteStartElement("x");
					xtw.WriteString(value);
					xtw.WriteEndElement();
					xtw.Flush();
					stream.Seek(0, SeekOrigin.Begin);
					var text = new StreamReader(stream).ReadToEnd();
					return XmlEscapeRegex.Match(text).Groups["xml"].Value;
				}
			}
		}
	}
}